//
// Created by LiuYou on 2021/4/30.
//
/**
 * @brief 这个文件是书上P148~150源码, 但是我现在不知道怎么使用它。不懂。
 */
#include <iostream>
#include <cygwin/in.h>
#include <arpa/inet.h>
#include <cassert>
#include <sys/unistd.h>
#include <cstring>
#include <libgen.h>
// 使用IDE运行命令按Ctrl + Enter可以使用相关的IDE功能（而不是终端）运行突出显示的操作。按Ctrl + Shift + Enter进行调试。按Enter像往常一样在终端中运行命令。您可以在“设置” |“打开”中关闭此行为。工具|终端。知道了！
// Run Commands using IDE Press Ctrl+Enter to run the highlighted action using the relevant IDE feature instead of the terminal. Press Ctrl+Shift+Enter for debug. Press Enter to run the command in the terminal as usual. You can turn this behavior on/off in Settings | Tools | Terminal. Got it!
/**
 * @brief 同时接受普通数据和带外数据
 *
 */
int main(int argc, char** argv) {
    if (argc <= 2) {
        // TODO 注意: basename(argv[0])宏返回的是生成的可执行文件的文件名称。
//        std::printf("usage: %s ip_address port_number\n", basename(argv[0]));
        std::printf("usage: %s IpAddress PortNumber\n", basename(argv[0]));
        return -1;
    }
    const char* ip = argv[1];
    int port = std::stoi(argv[2]);
    sockaddr_in serverSockAddrIn{
            .sin_family=PF_INET,
            .sin_port=htons(port),
            {.s_addr=0}
    };
    inet_pton(AF_INET, ip, &serverSockAddrIn.sin_addr);

    auto listenFD = socket(PF_INET, SOCK_STREAM, 0);
    assert(listenFD >= 0);
    auto ret = bind(listenFD, (sockaddr*) &serverSockAddrIn, sizeof(serverSockAddrIn));
    assert(ret != -1);
    ret = listen(listenFD, 5);
    assert(ret != -1);

    sockaddr_in clientSockAddrIn{};
    socklen_t clientSockAddrInLength = sizeof(clientSockAddrIn);
    auto connectFD = accept(listenFD, (sockaddr*) &clientSockAddrIn, (socklen_t*) &clientSockAddrIn);
    if (connectFD < 0) {
        std::printf("errno is: %d\n", errno);
        close(listenFD);
    }
    char buffer[1024];
    fd_set readFDS;
    fd_set exceptionFDS;
    FD_ZERO(&readFDS);
    FD_ZERO(&exceptionFDS);
    ssize_t retVal;
    for (;;) {
        std::memset(buffer, '\0', sizeof(buffer));
        /*
         * 每次调用select前都要重新在readFDS和exceptionFDS中设置文件描述符connectFD, 因为事件发生之后, 文件描述符集合将被内核修改。
         */
        FD_SET(connectFD, &readFDS);
        FD_SET(connectFD, &exceptionFDS);
        ret = select(connectFD + 1, &readFDS, NULL, &exceptionFDS, NULL);
        if (ret < 0) {
            std::cout << "selection failure" << std::endl;
            break;
        }
        /*
         * 对于可读事件, 采用普通的recv函数读取数据
         */
        if (FD_ISSET(connectFD, &readFDS)) {
            retVal = recv(connectFD, buffer, sizeof(buffer) - 1, 0);
            if (retVal <= 0) {
                break;
            }
            std::printf("get %zd bytes of normal data: %s\n", retVal, buffer);
        } else if (FD_ISSET(connectFD, &exceptionFDS)) {
            retVal = recv(connectFD, buffer, sizeof(buffer) - 1, MSG_OOB);
            if (retVal <= 0) {
                break;
            }
            std::printf("get %zd bytes of oob data: %s\n", retVal, buffer);
        }
    }
    close(connectFD);
    close(listenFD);


    return 0;
}